﻿// Copyright 2013 Ronald Schlenker, www.technewlogic.de.
// 
// This file is part of Byond.
// 
// Byond is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
// 
// Byond is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Byond.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using Autofac;
using Byond.AudioMidi;
using Byond.Frontend;
using Byond.Persistence;
using Byond.Standalone.MidiDotNetStuff;
using Byond.Standalone.PortAudioStuff;
using Byond.Standalone.ViewModels;
using Byond.Standalone.Views;
using Mvvm;
using NLog;
using PortAudioSharp;
using Utils.DialogManagement;
using IContainer = Autofac.IContainer;

namespace Byond.Standalone
{
	class StandaloneBootstrapper : FrontendBootstrapper
	{
		private readonly App _app;

		private SettingsManager _settingsManager;
		private PortAudioEngine _audioEngine;
		private DotNetMidi _dotNetMidi;
		private IDialogManager _dialogManager;
		private IDocumentManager _documentManager;
		private Logger _logger;
		private PersistenceManager _persistenceManager;
		private string _sessionPersistenceFileName;

		private bool _isClosing;
		private bool _quitOnClosing;

		public StandaloneBootstrapper(App app)
		{
			_app = app;
			_app.Startup += AppOnStartup;
			_app.Exit += AppOnExit;

			// Byond.Standalone
			RegisterAssemblyTypes(typeof(StandaloneBootstrapper).Assembly);

			ContainerBuilder.RegisterType<PortAudioEngine>().AsSelf().As<IAudio>().As<IAudioInfo>().SingleInstance();
			ContainerBuilder.RegisterType<DotNetMidi>().AsSelf().As<IMidi>().SingleInstance();

			ContainerBuilder.RegisterType<MainWindow>().SingleInstance();

			ContainerBuilder.RegisterType<HostControlsViewModel>();
			ContainerBuilder.RegisterType<HostControlsView>();

			ContainerBuilder.RegisterType<SettingsViewModel>();
			ContainerBuilder.RegisterType<SettingsView>();
			ContainerBuilder.RegisterType<AudioSettingsViewModel>();
			ContainerBuilder.RegisterType<AudioSettingsView>();
			ContainerBuilder.RegisterType<MidiSettingsViewModel>();
			ContainerBuilder.RegisterType<MidiSettingsView>();

			Build();
		}

		private void AppOnStartup(object sender, StartupEventArgs startupEventArgs)
		{
			InitializePersistence();
			InitializeRuntime();

			_settingsManager = Container.Resolve<SettingsManager>();
			_audioEngine = Container.Resolve<PortAudioEngine>();
			_dotNetMidi = Container.Resolve<DotNetMidi>();
			_dialogManager = Container.Resolve<IDialogManager>();
			_logger = Container.Resolve<Logger>();
			_persistenceManager = Container.Resolve<PersistenceManager>();
			_documentManager = Container.Resolve<IDocumentManager>();
			Container.Resolve<IEnsembleRunner>();
			_sessionPersistenceFileName = Container.Resolve<Env>().RootPath + "\\LastSession.dat";

			var viewFactory = Container.Resolve<ViewFactory>();
			Container.Resolve<MenuItemRegistrar>().RegisterItem(() =>
				(UIElement)viewFactory.GetViewForModel<HostControlsViewModel>());

			var mainWindow = Container.Resolve<MainWindow>();
			_app.MainWindow = mainWindow;
			mainWindow.Loaded += MainWindowOnLoaded;
			mainWindow.Closing += MainWindowOnClosing;

			_app.MainWindow.Show();
		}

		private void AppOnExit(object sender, ExitEventArgs exitEventArgs)
		{
			Dispose();
		}

		private void MainWindowOnLoaded(object sender, RoutedEventArgs routedEventArgs)
		{
			_app.MainWindow.Loaded -= MainWindowOnLoaded;

			InitializeUi(_app.MainWindow.Dispatcher);
			InitializeLibrary();
			InitializeMidi();
			InitializeAudio();
			RestoreLastSession();
		}

		private void MainWindowOnClosing(object sender, CancelEventArgs cancelEventArgs)
		{
			// Quit-Request: App schließen
			if (_quitOnClosing)
				return;

			// Erneutes Schließen, wenn Dialog noch offen ist: nichts...
			if (_isClosing)
			{
				cancelEventArgs.Cancel = true;
				return;
			}

			if (!_documentManager.DirtyDocuments.Any())
			{
				PersistCurrentState();
				return;
			}

			// Erstmal nicht quit und nachfragen...
			cancelEventArgs.Cancel = true;
			_isClosing = true;

			var dialog = _dialogManager.CreateMessageDialog(
				"Save all unmodified patches?",
				DialogMode.YesNoCancel);
			dialog.Cancel = () => _isClosing = false;
			dialog.Yes = () =>
				{
					PersistCurrentState();
					_documentManager.SaveAll();

					_quitOnClosing = true;
					_app.Shutdown();
				};
			dialog.No = () =>
				{
					PersistCurrentState();

					_quitOnClosing = true;
					_app.Shutdown();
				};

			dialog.Show();
		}

		private void PersistCurrentState()
		{
			if (!_persistenceManager.ShouldPersistCurrentState)
			{
				if (File.Exists(_sessionPersistenceFileName))
					File.Delete(_sessionPersistenceFileName);
			}
			else
			{
				var json = _persistenceManager.PersistCurrentStateToJson();
				File.WriteAllText(_sessionPersistenceFileName, json);
			}
		}

		private void RestoreLastSession()
		{
			// Restore last session
			try
			{
				if (File.Exists(_sessionPersistenceFileName))
				{
					var json = File.ReadAllText(_sessionPersistenceFileName);
					_persistenceManager.RestoreAndRunEnsemble(json);
				}
			}
			catch (Exception e)
			{
				_logger.ErrorException("Could not restore last session.", e);
				_dialogManager
					.CreateMessageDialog(e.Message, "Could not restore last session.", DialogMode.Ok)
					.Show();
			}
		}

		private void InitializeMidi()
		{
			try
			{
				var midiSettings = _settingsManager.TryGetSettings<MidiSettings>();
				if (midiSettings != null)
				{
					var inputDevice = midiSettings.GetInputDevice();
					if (inputDevice != null)
						_dotNetMidi.SetInputDevice(inputDevice);

					var outputDevice = midiSettings.GetOutputDevice();
					if (outputDevice != null)
						_dotNetMidi.SetOutputDevice(outputDevice);
				}
				else
					_dotNetMidi.UseDefaults();
			}
			catch (Exception e)
			{
				_logger.ErrorException("Could not initialize or restore midi engine.", e);
				_dialogManager
					.CreateMessageDialog(e.Message, "Could not initialize or restore midi engine.", DialogMode.Ok)
					.Show();
			}

		}

		private void InitializeAudio()
		{
			// Initialize Port Audio and read from last settings
			try
			{
				PortAudioExtensions.Initialize();

				var apiInfo = PortAudioExtensions.GetDefaultApi();
			    var inputDevice = apiInfo.defaultInputDevice != -1
			        ? PortAudio.Pa_GetDeviceInfo(apiInfo.defaultInputDevice)
			        : PortAudioExtensions.NoDevice;
                var outputDevice = apiInfo.defaultOutputDevice != -1
                    ? PortAudio.Pa_GetDeviceInfo(apiInfo.defaultOutputDevice)
                    : PortAudioExtensions.NoDevice;
				var sampleRate = (int)outputDevice.defaultSampleRate;
				var bufferSize = _audioEngine.BufferSize;

				var audioSettings = _settingsManager.TryGetSettings<AudioSettings>();
				if (audioSettings != null)
				{
					try
					{
						inputDevice = audioSettings.GetInputDevice();
						outputDevice = audioSettings.GetOutputDevice();
						sampleRate = audioSettings.SampleRate;
						bufferSize = audioSettings.BufferSize;
					}
					catch (Exception e)
					{
						_logger.ErrorException("Could not restore audio settings. Using defaults.", e);
						_dialogManager
							.CreateMessageDialog(e.Message, "Could not restore audio settings. Using defaults.", DialogMode.Ok)
							.Show();
					}
				}

				_audioEngine.ReStart(sampleRate, bufferSize, inputDevice, outputDevice);
			}
			catch (Exception e)
			{
				_logger.ErrorException("Could not initialize or restore audio engine.", e);
				_dialogManager
					.CreateMessageDialog(e.Message, "Could not initialize or restore audio engine.", DialogMode.Ok)
					.Show();
			}
		}
	}
}
